package org.codefinger.json.util.type;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Date;

import org.codefinger.json.JSONBase;
import org.codefinger.json.util.IdentityHashMap;
import org.codefinger.json.util.type.converter.BigDecimalConverter;
import org.codefinger.json.util.type.converter.BigIntegerConverter;
import org.codefinger.json.util.type.converter.BooleanConverter;
import org.codefinger.json.util.type.converter.ByteConverter;
import org.codefinger.json.util.type.converter.CharConverter;
import org.codefinger.json.util.type.converter.DateConverter;
import org.codefinger.json.util.type.converter.DoubleConverter;
import org.codefinger.json.util.type.converter.FloatConverter;
import org.codefinger.json.util.type.converter.IntConverter;
import org.codefinger.json.util.type.converter.JSONBaseConverter;
import org.codefinger.json.util.type.converter.LongConverter;
import org.codefinger.json.util.type.converter.ShortConverter;
import org.codefinger.json.util.type.converter.StringConverter;

public final class TypeUtil {

	private static final IdentityHashMap<Class<?>, TypeConverter>	TYPECONVERTER_MAP	= new IdentityHashMap<Class<?>, TypeConverter>();

	static {
		TYPECONVERTER_MAP.put(Integer.class, IntConverter.INSTANCE);
		TYPECONVERTER_MAP.put(Double.class, DoubleConverter.INSTANCE);
		TYPECONVERTER_MAP.put(Float.class, FloatConverter.INSTANCE);
		TYPECONVERTER_MAP.put(Long.class, LongConverter.INSTANCE);
		TYPECONVERTER_MAP.put(Short.class, ShortConverter.INSTANCE);
		TYPECONVERTER_MAP.put(Byte.class, ByteConverter.INSTANCE);
		TYPECONVERTER_MAP.put(Character.class, CharConverter.INSTANCE);
		TYPECONVERTER_MAP.put(Boolean.class, BooleanConverter.INSTANCE);
		TYPECONVERTER_MAP.put(String.class, StringConverter.INSTANCE);
		TYPECONVERTER_MAP.put(BigDecimal.class, BigDecimalConverter.INSTANCE);
		TYPECONVERTER_MAP.put(BigInteger.class, BigIntegerConverter.INSTANCE);
		TYPECONVERTER_MAP.put(Date.class, DateConverter.INSTANCE);
		TYPECONVERTER_MAP.put(JSONBase.class, JSONBaseConverter.INSTANCE);
	}

	private TypeUtil() {

	}

	public static Integer castToInt(Object value) {
		if (value == null) {
			return null;
		}

		if (value instanceof Integer) {
			return (Integer) value;
		}

		TypeConverter converter = getTypeConverter(value.getClass());

		if (converter == null) {
			return null;
		}

		return converter.castToInt(value);
	}

	public static Character castToChar(Object value) {
		if (value == null) {
			return null;
		}

		if (value instanceof Character) {
			return (Character) value;
		}

		TypeConverter converter = getTypeConverter(value.getClass());

		if (converter == null) {
			return null;
		}

		return converter.castToChar(value);
	}

	public static Byte castToByte(Object value) {
		if (value == null) {
			return null;
		}

		if (value instanceof Byte) {
			return (Byte) value;
		}

		TypeConverter converter = getTypeConverter(value.getClass());

		if (converter == null) {
			return null;
		}

		return converter.castToByte(value);
	}

	public static Short castToShort(Object value) {
		if (value == null) {
			return null;
		}

		if (value instanceof Short) {
			return (Short) value;
		}

		TypeConverter converter = getTypeConverter(value.getClass());

		if (converter == null) {
			return null;
		}

		return converter.castToShort(value);
	}

	public static Long castToLong(Object value) {
		if (value == null) {
			return null;
		}

		if (value instanceof Long) {
			return (Long) value;
		}

		TypeConverter converter = getTypeConverter(value.getClass());

		if (converter == null) {
			return null;
		}

		return converter.castToLong(value);
	}

	public static Float castToFloat(Object value) {
		if (value == null) {
			return null;
		}

		if (value instanceof Float) {
			return (Float) value;
		}

		TypeConverter converter = getTypeConverter(value.getClass());

		if (converter == null) {
			return null;
		}

		return converter.castToFloat(value);
	}

	public static Double castToDouble(Object value) {
		if (value == null) {
			return null;
		}

		if (value instanceof Double) {
			return (Double) value;
		}

		TypeConverter converter = getTypeConverter(value.getClass());

		if (converter == null) {
			return null;
		}

		return converter.castToDouble(value);
	}

	public static Boolean castToBoolean(Object value) {
		if (value == null) {
			return null;
		}

		if (value instanceof Boolean) {
			return (Boolean) value;
		}

		TypeConverter converter = getTypeConverter(value.getClass());

		if (converter == null) {
			return null;
		}

		return converter.castToBoolean(value);
	}

	public static String castToString(Object value) {
		if (value == null) {
			return null;
		}

		if (value instanceof String) {
			return (String) value;
		}

		TypeConverter converter = getTypeConverter(value.getClass());

		if (converter == null) {
			return value.toString();
		}

		return converter.castToString(value);
	}

	public static BigDecimal castToBigDecimal(Object value) {
		if (value == null) {
			return null;
		}

		if (value instanceof BigDecimal) {
			return (BigDecimal) value;
		}

		TypeConverter converter = getTypeConverter(value.getClass());

		if (converter == null) {
			return null;
		}

		return converter.castToBigDecimal(value);
	}

	public static BigInteger castToBigInteger(Object value) {
		if (value == null) {
			return null;
		}

		if (value instanceof BigInteger) {
			return (BigInteger) value;
		}

		TypeConverter converter = getTypeConverter(value.getClass());

		if (converter == null) {
			return null;
		}

		return converter.castToBigInteger(value);
	}

	public static Date castToDate(Object value) {
		if (value == null) {
			return null;
		}

		if (value instanceof Date) {
			return (Date) value;
		}

		TypeConverter converter = getTypeConverter(value.getClass());

		if (converter == null) {
			return null;
		}

		return converter.castToDate(value);
	}

	public static TypeConverter getTypeConverter(Class<?> clazz) {
		TypeConverter converter = TYPECONVERTER_MAP.get(clazz);
		if (converter == null) {
			if (Date.class.isAssignableFrom(clazz)) {
				converter = DateConverter.INSTANCE;
				TYPECONVERTER_MAP.put(clazz, converter);
			} else {
				return null;
			}
		}
		return converter;
	}

}
